﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;

namespace DARE
{
    /// <summary>
    /// This class manages the mouse events.
    /// </summary>
    /// <example>
    /// <code>
    /// foreach (DARE.CMouseButtonEvent button in m_Dare.InputMgr.Mouse.ButtonPressedEvent)
    /// {
    ///     switch (button.Button)
    ///     {
    ///         case DARE.CMouse.EButton.LEFT: ProcessRayCast(); break;
    ///         case DARE.CMouse.EButton.MIDDLE: break;
    ///         case DARE.CMouse.EButton.RIGHT: break;
    ///     }
    /// }
    /// </code>
    /// </example>
    public class CMouse : CInputProvider<MouseState>
    {
        #region identifier

        public enum EButton
        {
            LEFT,
            RIGHT,
            MIDDLE
        }

        public delegate void MouseEventHandler(EButton button, ButtonState state);

        #endregion

        #region fields

        private CMouseMoveEvent m_mouseMoveEvent;

        private List<CMouseWheelEvent> m_mouseWheelEvent;
        private List<EButton> m_mouseButtonPressedEvent;
        private List<EButton> m_mouseButtonReleasedEvent;
        private Dictionary<EButton, List<CGameObject>> m_registeredGameObjects;

        private Dictionary<EButton, List<MouseEventHandler>> m_listeners = new Dictionary<EButton, List<MouseEventHandler>>();

        private int lastWheel;
        private Vector2 m_position;

        private Dictionary<CRender3D, Dictionary<CNode, CRayCast.CIntersectInfo>> m_mouseIntersectInfo;
        private Dictionary<CHUDSystem, List<CWidget>> m_cursorHUDIntersections;
        private bool m_autoCheckMouseIntersectInfo = true;

        private bool m_leftHasBeenPressed = false;
        private bool m_rightHasBeenPressed = false;
        private bool m_middleHasBeenPressed = false;
        private bool m_centered = false;

        private bool m_autoReset = true;
        private MouseState m_mouseState;

        #endregion

        #region properties

        public CMouseMoveEvent MoveEvent { get { return m_mouseMoveEvent; } }
        public List<CMouseWheelEvent> WheelEvent { get { return m_mouseWheelEvent; } }
        public List<EButton> ButtonPressedEvent { get { return m_mouseButtonPressedEvent; } }
        public List<EButton> ButtonReleasedEvent { get { return m_mouseButtonReleasedEvent; } }

        public Dictionary<CRender3D, Dictionary<CNode, CRayCast.CIntersectInfo>> MouseIntersectInfo { get { return m_mouseIntersectInfo; } }
        public Dictionary<CHUDSystem, List<CWidget>> CursorHUDIntersections { get { return m_cursorHUDIntersections; } }
        public bool AutoCheckMouseIntersectInfo { get { return m_autoCheckMouseIntersectInfo; } set { m_autoCheckMouseIntersectInfo = value; } }

        public bool LeftIsPressed { get { return m_leftHasBeenPressed; } }
        public bool RightIsPressed { get { return m_rightHasBeenPressed; } }
        public bool MiddleIsPressed { get { return m_middleHasBeenPressed; } }

        /// <summary>
        /// gets the 2D position of the cursor.
        /// </summary>
        public Vector2 Position { get { return m_position; } }
        public bool IsCentered { get { return m_centered; } set { m_centered = value; m_mouseMoveEvent.IsCentered = value; } }
        public bool AutoReset { get { return m_autoReset; } set { m_autoReset = value; } }

        public bool HasMoved { get { if (m_mouseMoveEvent.LastPosition != m_mouseMoveEvent.CurrentPosition) return true; return false; } }
        public MouseState MouseState { get { return m_mouseState; } }

        #endregion

        #region ctor

        public CMouse()
        {
            m_mouseButtonPressedEvent = new List<EButton>();
            m_mouseButtonReleasedEvent = new List<EButton>();
            m_mouseMoveEvent = new CMouseMoveEvent();
            m_mouseWheelEvent = new List<CMouseWheelEvent>();
            Mouse.SetPosition(CDare.Instance.GraphicsDevice.Viewport.Width / 2, CDare.Instance.GraphicsDevice.Viewport.Height / 2);
            MouseState ms = Mouse.GetState();
            m_position = new Vector2(ms.X, ms.Y);
            lastWheel = ms.ScrollWheelValue;
            m_registeredGameObjects = new Dictionary<EButton, List<CGameObject>>();
            m_mouseIntersectInfo = new Dictionary<CRender3D, Dictionary<CNode, CRayCast.CIntersectInfo>>();
            m_cursorHUDIntersections = new Dictionary<CHUDSystem, List<CWidget>>();
            m_listeners.Add(EButton.LEFT, new List<MouseEventHandler>());
            m_listeners.Add(EButton.RIGHT, new List<MouseEventHandler>());
            m_listeners.Add(EButton.MIDDLE, new List<MouseEventHandler>());
            base.RegisterProvider(new CXnaMouseProvider());
        }

        #endregion

        #region methods

        public CMouse RemoveListener(CGameObject gameObject)
        {
            foreach (List<CGameObject> list in m_registeredGameObjects.Values)
            {
                if (list.Contains(gameObject))
                    list.Remove(gameObject);
            }
            return this;
        }

        public CMouse RemoveAllListeners()
        {
            m_listeners.Clear();
            return this;
        }

        public CMouse RemoveListener(EButton button, MouseEventHandler handler)
        {
            if (m_listeners[button] != null)
                m_listeners[button].Remove(handler);
            return this;
        }

        public CMouse RegisterListener(EButton button, MouseEventHandler handler)
        {
            if (m_listeners[button] == null)
                m_listeners[button] = new List<MouseEventHandler>();
            m_listeners[button].Add(handler);
            return this;
        }

        public CGameObject RegisterGameObjectOnButton(CGameObject go, EButton button)
        {
            if (!m_registeredGameObjects.ContainsKey(button))
                m_registeredGameObjects[button] = new List<CGameObject>();
            m_registeredGameObjects[button].Add(go);
            return go;
        }

        public CGameObject UnRegisterGameObjectOnButton(CGameObject go, EButton button)
        {
            if (m_registeredGameObjects.ContainsKey(button))
                m_registeredGameObjects[button].Remove(go);
            return go;
        }

        public CGameObject UnRegisterGameObjectOnAllButton(CGameObject go)
        {
            foreach (List<CGameObject> list in m_registeredGameObjects.Values)
                if (list.Contains(go))
                    list.Remove(go);
            return go;
        }

        public void UnRegisterAllGameObjects()
        {
            foreach (List<CGameObject> list in m_registeredGameObjects.Values)
                list.Clear();
            m_registeredGameObjects.Clear();
        }

        public void Clear()
        {
            m_mouseButtonPressedEvent.Clear();
            m_mouseButtonReleasedEvent.Clear();
            m_mouseWheelEvent.Clear();
            m_mouseIntersectInfo.Clear();
        }

        public bool IsConsumerUnderCursor()
        {
            foreach (List<CWidget> wids in m_cursorHUDIntersections.Values)
                foreach (CWidget wid in wids)
                    if (wid.IsInputConsumer)
                        return true;
            return false;
        }

        public void Update()
        {
            if (!base.ProviderEnable)
                return;
            m_mouseState = base.GetState();
            
            if (!CDare.Instance.Game.IsActive)
                return;
            m_mouseMoveEvent.CurrentPosition = new Microsoft.Xna.Framework.Vector2(m_mouseState.X, m_mouseState.Y);
            m_position = m_mouseMoveEvent.CurrentPosition;

            if (m_autoCheckMouseIntersectInfo)
            {
                m_cursorHUDIntersections.Clear();
                foreach (CHUDSystem hud in CDare.Instance.SceneMgr.ActiveScene.HUDSystem.Values)
                    m_cursorHUDIntersections.Add(hud, hud.GetElementsUnderCursor(this));
                m_mouseIntersectInfo.Clear();
                foreach (CRender3D render in CDare.Instance.SceneMgr.ActiveScene.Render3D.Values)
                {
                    if (render.CameraMgr.ActiveCamera == null)
                        continue;
                    Dictionary<CNode, CRayCast.CIntersectInfo> dico = new Dictionary<CNode, CRayCast.CIntersectInfo>();
                    foreach (CRayCast.CIntersectInfo iInfo in render.RayCast.IntersectOrderFromCenterToMouse(100, 10))
                    {
                        if (!dico.ContainsKey(iInfo.node))
                            dico.Add(iInfo.node, iInfo);
                    }
                    m_mouseIntersectInfo.Add(render, dico);
                }
            }
            bool isInputConsumerUnder = IsConsumerUnderCursor();

            if (m_mouseState.ScrollWheelValue - lastWheel != 0)
            {
                CMouseWheelEvent cmwe = new CMouseWheelEvent();
                cmwe.TotalWheel = m_mouseState.ScrollWheelValue;
                cmwe.WheelValue = m_mouseState.ScrollWheelValue - lastWheel;
                lastWheel = m_mouseState.ScrollWheelValue;
                WheelEvent.Add(cmwe);
            }
            if (m_mouseState.LeftButton == ButtonState.Pressed && !m_leftHasBeenPressed)
            {
                if (m_listeners[EButton.LEFT] != null)
                    foreach (MouseEventHandler e in m_listeners[EButton.LEFT])
                        e.Invoke(EButton.LEFT, ButtonState.Pressed);
                m_mouseButtonPressedEvent.Add(EButton.LEFT);
                if (m_registeredGameObjects.ContainsKey(EButton.LEFT))
                    for (int i = 0; i < m_registeredGameObjects[EButton.LEFT].Count; ++i)
                    {
                        CGameObject go = m_registeredGameObjects[EButton.LEFT].ElementAt(i);
                        if (!isInputConsumerUnder && go.Node != null && (!go.IsSelfish || ((m_mouseIntersectInfo.ContainsKey(go.Node.Render3D) && m_mouseIntersectInfo[go.Node.Render3D].ContainsKey(go.Node)))))
                            go.OnClickMethod(EButton.LEFT);
                    }
                m_leftHasBeenPressed = true;
            }
            else if (m_mouseState.LeftButton == ButtonState.Released && m_leftHasBeenPressed)
            {
                if (m_listeners[EButton.LEFT] != null)
                    foreach (MouseEventHandler e in m_listeners[EButton.LEFT])
                        e.Invoke(EButton.LEFT, ButtonState.Released);
                m_mouseButtonReleasedEvent.Add(EButton.LEFT);
                if (m_registeredGameObjects.ContainsKey(EButton.LEFT))
                    for (int i = 0; i < m_registeredGameObjects[EButton.LEFT].Count; ++i)
                    {
                        CGameObject go = m_registeredGameObjects[EButton.LEFT].ElementAt(i);
                        if (go.IsLeftClicked && go.Node != null && (!go.IsSelfish || ((m_mouseIntersectInfo.ContainsKey(go.Node.Render3D) && m_mouseIntersectInfo[go.Node.Render3D].ContainsKey(go.Node)))))
                            go.OnReleaseMethod(EButton.LEFT);
                    }
                m_leftHasBeenPressed = false;
            }
            if (m_mouseState.MiddleButton == ButtonState.Pressed && !m_middleHasBeenPressed)
            {
                if (m_listeners[EButton.MIDDLE] != null)
                    foreach (MouseEventHandler e in m_listeners[EButton.MIDDLE])
                        e.Invoke(EButton.MIDDLE, ButtonState.Pressed);
                m_mouseButtonPressedEvent.Add(EButton.MIDDLE);
                if (m_registeredGameObjects.ContainsKey(EButton.MIDDLE))
                    for (int i = 0; i < m_registeredGameObjects[EButton.MIDDLE].Count; ++i)
                    {
                        CGameObject go = m_registeredGameObjects[EButton.MIDDLE].ElementAt(i);
                        if (!isInputConsumerUnder && go.Node != null && (!go.IsSelfish || ((m_mouseIntersectInfo.ContainsKey(go.Node.Render3D) && m_mouseIntersectInfo[go.Node.Render3D].ContainsKey(go.Node)))))
                            go.OnClickMethod(EButton.MIDDLE);
                    }
                m_middleHasBeenPressed = true;
            }
            else if (m_mouseState.MiddleButton == ButtonState.Released && m_middleHasBeenPressed)
            {
                if (m_listeners[EButton.MIDDLE] != null)
                    foreach (MouseEventHandler e in m_listeners[EButton.MIDDLE])
                        e.Invoke(EButton.MIDDLE, ButtonState.Released);
                m_mouseButtonReleasedEvent.Add(EButton.MIDDLE);
                if (m_registeredGameObjects.ContainsKey(EButton.MIDDLE))
                    for (int i = 0; i < m_registeredGameObjects[EButton.MIDDLE].Count; ++i)
                    {
                        CGameObject go = m_registeredGameObjects[EButton.MIDDLE].ElementAt(i);
                        if (go.IsRightClicked && go.Node != null && (!go.IsSelfish || ((m_mouseIntersectInfo.ContainsKey(go.Node.Render3D) && m_mouseIntersectInfo[go.Node.Render3D].ContainsKey(go.Node)))))
                            go.OnReleaseMethod(EButton.MIDDLE);
                    }
                m_middleHasBeenPressed = false;
            }
            if (m_mouseState.RightButton == ButtonState.Pressed && !m_rightHasBeenPressed)
            {
                if (m_listeners[EButton.RIGHT] != null)
                    foreach (MouseEventHandler e in m_listeners[EButton.RIGHT])
                        e.Invoke(EButton.RIGHT, ButtonState.Pressed);
                m_mouseButtonPressedEvent.Add(EButton.RIGHT);
                if (m_registeredGameObjects.ContainsKey(EButton.RIGHT))
                    for (int i = 0; i < m_registeredGameObjects[EButton.RIGHT].Count; ++i)
                    {
                        CGameObject go = m_registeredGameObjects[EButton.RIGHT].ElementAt(i);
                        if (go.IsMiddleClicked && go.Node != null && (!go.IsSelfish || ((m_mouseIntersectInfo.ContainsKey(go.Node.Render3D) && m_mouseIntersectInfo[go.Node.Render3D].ContainsKey(go.Node)))))
                            go.OnClickMethod(EButton.RIGHT);
                    }
                m_rightHasBeenPressed = true;
            }
            else if (m_mouseState.RightButton == ButtonState.Released && m_rightHasBeenPressed)
            {
                if (m_listeners[EButton.RIGHT] != null)
                    foreach (MouseEventHandler e in m_listeners[EButton.RIGHT])
                        e.Invoke(EButton.RIGHT, ButtonState.Released);
                m_mouseButtonReleasedEvent.Add(EButton.RIGHT);
                if (m_registeredGameObjects.ContainsKey(EButton.RIGHT))
                    for (int i = 0; i < m_registeredGameObjects[EButton.RIGHT].Count; ++i)
                    {
                        CGameObject go = m_registeredGameObjects[EButton.RIGHT].ElementAt(i);
                        if (!isInputConsumerUnder && go.Node != null && (!go.IsSelfish || ((m_mouseIntersectInfo.ContainsKey(go.Node.Render3D) && m_mouseIntersectInfo[go.Node.Render3D].ContainsKey(go.Node)))))
                            go.OnReleaseMethod(EButton.RIGHT);
                    }
                m_rightHasBeenPressed = false;
            }
            if (m_centered)
                Mouse.SetPosition((int)(CDare.Instance.GraphicsDevice.Viewport.Width / 2.0f), (int)(CDare.Instance.GraphicsDevice.Viewport.Height / 2.0f));
            if (m_autoReset)
                m_mouseMoveEvent.Reset();
        }
        
        #endregion

    }
}
